package factory

import (
	"context"
	"fmt"

	"code.byted.org/bytertc/whiteboard_server/biz/service/action/adjust"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action/cursor"

	"code.byted.org/bytertc/whiteboard_server/biz/service/action/lock"

	bytedtracer "code.byted.org/bytedtrace/interface-go"
	v1 "code.byted.org/bytertc/whiteboard_server/biz/api/pb/v1"
	"code.byted.org/bytertc/whiteboard_server/biz/dal"
	"code.byted.org/bytertc/whiteboard_server/biz/pkg/trace"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action/base"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action/board"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action/draw"
	"code.byted.org/bytertc/whiteboard_server/biz/service/action/erase"
	"code.byted.org/gopkg/logs/v2/log"
)

func NewActions(ctx context.Context, appId, roomId string, packet *v1.Packet, repo dal.Repo) ([]action.Action, error) {
	actionsSet := classifyActions(ctx, packet)
	info := action.Info{
		Operator: packet.Operator,
		AppId:    appId,
		RoomId:   roomId,
		BoardId:  packet.BoardId,
		PageId:   packet.PageId,
	}
	baseAct := base.Action{Info: info, Repo: repo}

	var err error
	var act action.Action
	actList := make([]action.Action, 0, len(actionsSet))
	for _, rawActs := range actionsSet {
		switch rawActs[0].Type {
		case v1.ActionType_kErase, v1.ActionType_kClearBoard:
			act, err = erase.NewAction(ctx, baseAct, rawActs[0])
		case v1.ActionType_kCreatePage, v1.ActionType_kFlipPage, v1.ActionType_kDeletePage, v1.ActionType_kChangePageBackground, v1.ActionType_kChangeBoardBackground, v1.ActionType_kSetActiveBoard, v1.ActionType_kCloseBoard, v1.ActionType_kZoomBoard, v1.ActionType_kStepChange, v1.ActionType_kPptMediaChange, v1.ActionType_kPptTriggerChange:
			act, err = board.NewAction(ctx, baseAct, rawActs[0])
		case v1.ActionType_kLock, v1.ActionType_kUnLock:
			act, err = lock.NewAction(ctx, baseAct, rawActs[0])
		case v1.ActionType_kTransform:
			act, err = adjust.NewAction(ctx, baseAct, rawActs)
		case v1.ActionType_kMoveCursor, v1.ActionType_kChangeCursorState:
			act, err = cursor.NewAction(ctx, baseAct, rawActs[0])
		default:
			act, err = draw.NewAction(ctx, baseAct, rawActs)
		}
		bytedtracer.AddEvents(ctx, bytedtracer.NewEvent(trace.EventTypeBiz, trace.ActionNew.String()).
			SetTag("_is_error", err != nil).SetTag("type", rawActs[0].Type.String()).SetEmitLog(true))
		if err != nil {
			return nil, err
		}
		actList = append(actList, act)
	}
	return actList, nil
}

func classifyActions(ctx context.Context, packet *v1.Packet) [][]*v1.Action {
	actionsSet := make([][]*v1.Action, 0)
	actionMap := make(map[string]int)
	// 需要保持各组 action 之间的顺序，将同一组(key相同)的 action 放在一起
	for _, act := range packet.Actions {
		key := actionKey(packet.BoardId, packet.PageId, act)
		index, exist := actionMap[key]
		if exist {
			actionsSet[index] = append(actionsSet[index], act)
			log.V1.CtxInfo(ctx, "old action %s, phases %d", key, len(actionsSet[index]))
		} else {
			actionsSet = append(actionsSet, []*v1.Action{act})
			actionMap[key] = len(actionsSet) - 1
			log.V1.CtxInfo(ctx, "new action %s, index %d", key, len(actionsSet)-1)
		}
	}
	return actionsSet
}

func actionKey(boardId int32, page string, action *v1.Action) string {
	obj := action.ObjId
	if len(obj) == 0 {
		obj = "-"
	}
	return fmt.Sprintf("%d.%s.%s.%d.%d", boardId, page, obj, action.ActionId, action.Type)
}
